package com.sudo.sso.server.api.sso;

import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.sso.processor.SaSsoServerProcessor;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.boot.starter.redis.component.RedisKeys;
import com.sudo.boot.base.constant.SsoConstant;
import com.sudo.boot.base.constant.SysConstant;
import com.sudo.boot.base.constant.enums.EnumScanLogin;
import com.sudo.boot.base.constant.enums.EnumStatus;
import com.sudo.boot.base.constant.enums.EnumYesNo;
import com.sudo.boot.base.exception.BizException;
import com.sudo.boot.base.util.PatternUtil;
import com.sudo.sso.server.constant.EnumLoginType;
import com.sudo.sso.server.dto.SysRole$LoginDto;
import com.sudo.sso.server.entity.SysUser;
import com.sudo.sso.server.service.SysRoleService;
import com.sudo.sso.server.service.SysUserService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import java.util.*;

/**
 * Sa-Token-SSO Server端 Controller 
 * @author click33
 *
 */
@RequestMapping("/ss/sso")
@RestController
public class SsoServerController {
	@Resource
	private SysUserService sysUserService;
	@Resource
	private SysRoleService sysRoleService;

	@Resource
	private SaTokenDao saTokenDao;


	/*
	 * SSO-Server端：处理所有SSO相关请求 
	 * 		http://{host}:{port}/sso/auth			-- 单点登录授权地址，接受参数：redirect=授权重定向地址 
	 * 		http://{host}:{port}/sso/doLogin		-- 账号密码登录接口，接受参数：name、pwd 
	 * 		http://{host}:{port}/sso/checkTicket	-- Ticket校验接口（isHttp=true时打开），接受参数：ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选] 
	 * 		http://{host}:{port}/sso/signout		-- 单点注销地址（isSlo=true时打开），接受参数：loginId=账号id、secretkey=接口调用秘钥 
	 */
	@RequestMapping("/auth")
	public Object ssoRequest() {
		return SaSsoServerProcessor.instance.ssoAuth();
	}

	@RequestMapping("/doLogin")
	public Object ssoDoLogin() {
		return SaSsoServerProcessor.instance.ssoDoLogin();
	}

	@RequestMapping("/checkTicket")
	public Object ssoCheckTicket() {
		return SaSsoServerProcessor.instance.ssoCheckTicket();
	}

	@RequestMapping("/signout")
	public Object ssoSignout() {
		return SaSsoServerProcessor.instance.ssoSignout();
	}
	// 配置SSO相关参数 
	@Autowired
	private void configSso(SaSsoServerConfig ssoServer) {
		// 配置：未登录时返回的View
		ssoServer.notLoginView = () -> new ModelAndView("login.html");

		// 配置：登录处理函数 
		ssoServer.doLoginHandle = (name, pwd) -> {
			HttpServletRequest request = SpringMVCUtil.getRequest();
			String type = request.getParameter("type");
			SysUser sysUser;
			if (EnumLoginType.SCAN.getCode().equals(type)) {
				// 扫码登录
				String loginCode = request.getParameter("loginCode");
				Long userId = checkLoginStatus(loginCode);
				sysUser = sysUserService.getById(userId);
			}else if (EnumLoginType.PASSWORD.getCode().equals(type)) {
				// 密码登录
				boolean matchName = PatternUtil.isMatches(PatternUtil.U_P_R_M_D, name);
				Assert.isTrue(matchName, "用户名格式不正确");
				boolean matchPwd = PatternUtil.isMatches(PatternUtil.U_P_R_M_D, pwd);
				Assert.isTrue(matchPwd, "密码格式不正确");
				// 用户登录
				String encrypt = SaSecureUtil.aesEncrypt(SysConstant.none, pwd);
				sysUser = sysUserService.checkUserLogin(name, encrypt);
			}else {
				throw new SaSsoException("未知的登录方式");
			}

			// 查询用户角色信息
			List<SysRole$LoginDto> role$LoginDtoList = sysRoleService.selectRoleListByUsername(sysUser.getUsername());
			if (CollectionUtils.isEmpty(role$LoginDtoList)) {
				throw new SaSsoException("该用户未分配角色");
			}

			// 判断用户状态
			if (!EnumStatus.ON.getCode().equals(sysUser.getStatus())) {
				throw new BizException("该用户已锁定");
			}
			// 设置当前用户的角色
			String currRole = getCurrRole(role$LoginDtoList);
			StpUtil.login(sysUser.getUsername());
			StpUtil.getSession().set(SsoConstant.currUserStatus, EnumStatus.ON.getCode());
			StpUtil.getSession().set(SsoConstant.currRoleCode, currRole);

			// 设置当前用户ID
			StpUtil.getSession().set(SsoConstant.currUserId, sysUser.getId());

			// 当前角色信息
			SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
			return SaResult.ok("登录成功！").setData(tokenInfo);
		};

	}

	private Long checkLoginStatus(String loginCode) {
		Object loginStatus = saTokenDao.get(RedisKeys.scanLoginStatus(loginCode));
		if (Objects.isNull(loginStatus) || Objects.equals(EnumScanLogin.LOGIN_FAIL.getCode(), loginStatus.toString())) {
			throw new SaSsoException(EnumScanLogin.LOGIN_FAIL.getDesc());
		}
		if (EnumScanLogin.UNBIND.getCode().equals(loginStatus)) {
			throw new SaSsoException(EnumScanLogin.UNBIND.getDesc());
		}
		Object userId = saTokenDao.get(RedisKeys.scanLoginUserId(loginCode));
		if (Objects.isNull(userId)) {
			throw new SaSsoException("登录超时");
		}
		// 删除标识
		saTokenDao.delete(RedisKeys.scanLoginStatus(loginCode));
		saTokenDao.delete(RedisKeys.scanLoginUserId(loginCode));
		return Long.valueOf(String.valueOf(userId));
	}

	private String getCurrRole(List<SysRole$LoginDto> role$LoginDtoList) {

		// 默认角色
		SysRole$LoginDto defaultRole = null;
		List<SysRole$LoginDto> otherRoleList = new ArrayList<>();
		for (SysRole$LoginDto loginDto : role$LoginDtoList) {
			if (EnumYesNo.YES.getCode().equals(loginDto.getDefaultRole())) {
				defaultRole = loginDto;
			}else {
				otherRoleList.add(loginDto);
			}
		}
		Assert.notNull(defaultRole,"缺少默认角色，请联系管理员");


		// 如果默认角色没有禁用，则直接登录
		if (EnumStatus.ON.getCode().equals(defaultRole.getStatus())) {
			// 写入当前用户角色信息
			String roleStatus = RedisKeys.roleStatus(defaultRole.getRoleCode());
			if (Objects.isNull(saTokenDao.get(roleStatus))) {
				saTokenDao.set(roleStatus, EnumStatus.ON.getCode(),-1);
			}
			return defaultRole.getRoleCode();
		}

		// 如果默认角色已禁用，则找到切换角色登录
		// 判断是否存在其他角色
		Assert.notEmpty(otherRoleList,"该帐号唯一角色被禁用，禁止登录");

		// 找到未禁用的角色
		List<SysRole$LoginDto> roleOnStatus = otherRoleList.stream()
				.filter(o -> EnumStatus.ON.getCode().equals(o.getStatus())).toList();
		Assert.notEmpty(roleOnStatus,"该帐号所有角色被禁用，禁止登录");

		// 随机获取一个角色登录
		String currRoleCode = roleOnStatus.get(0).getRoleCode();
		// 写入当前用户角色信息
		String roleStatus = RedisKeys.roleStatus(currRoleCode);
		// 角色状态
		if (Objects.isNull(saTokenDao.get(roleStatus))) {
			saTokenDao.set(roleStatus, EnumStatus.ON.getCode(),-1);
		}
		return currRoleCode;
	}

}
